import Singleton from "../Base/Singleton"
import { ApiMsgEnum, binaryDecode, binaryEncode, IModel, strdecode, strencode } from "../Common"

interface IItem {
    cb: Function
    ctx: unknown
}

interface ICallApiRet<T> {
    success: boolean
    res?: T
    error?: Error
}

export default class NetworkManager extends Singleton {
    static get Instance() {
        return super.GetInstance<NetworkManager>()
    }

    isConnected = false
    port = 9876
    ws: WebSocket
    private map: Map<ApiMsgEnum, Array<IItem>> = new Map() // 类似事件订阅的机制，感觉有点像 socket.io 的实现

    connect() {
        return new Promise((resolve, reject) => {
            if (this.isConnected) {
                resolve(true)
                return
            }

            this.ws = new WebSocket(`ws://localhost:${this.port}`)
            this.ws.binaryType = "arraybuffer" // 设置二进制类型为 ArrayBuffer

            this.ws.onopen = () => {
                this.isConnected = true
                console.log("ws 连接成功")
                resolve(true)
            }

            this.ws.onclose = () => {
                this.isConnected = false
                console.log("ws 连接关闭")
            }

            this.ws.onerror = (error) => {
                this.isConnected = false
                console.error("ws 连接失败", error)
                reject(false)
            }

            this.ws.onmessage = (event) => {
                try {
                    // console.log("ws 收到消息:", event.data)
                    // const json = JSON.parse(event.data)
                    
                    // const ta = new Uint8Array(event.data)
                    // const str = strdecode(ta)
                    // const json = JSON.parse(str)

                    const json = binaryDecode(event.data)
                    const { name, data } = json
                    if (this.map.has(name)) {
                        this.map.get(name).forEach(({ cb, ctx }) => {
                            cb.call(ctx, data)
                        })
                    }
                } catch (e) {
                    console.error(e)
                }
            }
        })
    }

    // callApi(name: string, data): Promise<ICallApiRet> {
    callApi<T extends keyof IModel['api']>(name: T, data: IModel['api'][T]['req']): Promise<ICallApiRet<IModel['api'][T]['res']>> {
        return new Promise((resolve) => {
            try {
                const timer = setTimeout(() => {
                    resolve({ success: false, error: new Error("请求超时") })
                    this.unlistenMsg(name as any, cb, this)
                }, 5000)

                const cb = (res) => {
                    resolve(res)
                    clearTimeout(timer)
                    this.unlistenMsg(name as any, cb, this)
                }
                this.listenMsg(name as any, cb, this)
                this.sendMsg(name as any, data)
            } catch (error) {
                resolve({ success: false, error })
            }

        })
    }

    // sendMsg(name: string, data) {
    async sendMsg<T extends keyof IModel['msg']>(name: T, data: IModel['msg'][T]) {
        // const msg = {
        //     name,
        //     data
        // }
        // const str = JSON.stringify(msg)
        // const ta = strencode(str)
        // const ab = new ArrayBuffer(ta.length)
        // const da = new DataView(ab)
        // for (let index = 0; index < ta.length; index++) {
        //     da.setUint8(index, ta[index])
        // }
        // // await new Promise((rs)=>setTimeout(rs, 2000)) // 模拟延时 2 秒
        const da = binaryEncode(name, data)
        this.ws.send(da.buffer)
        // this.ws.send(JSON.stringify(msg))
    }

    // listenMsg(name: string, cb: Function, ctx: unknown) {
    listenMsg<T extends keyof IModel['msg']>(name: T, cb: (args: IModel['msg'][T]) => void, ctx: unknown) {
        if (this.map.has(name)) {
            this.map.get(name).push({ cb, ctx })
        } else {
            this.map.set(name, [{ cb, ctx }])
        }
    }

    // unlistenMsg(name: string, cb: Function, ctx: unknown) {
    unlistenMsg<T extends keyof IModel['msg']>(name: T, cb: (args: IModel['msg'][T]) => void, ctx: unknown) {
        if (this.map.has(name)) {
            const index = this.map.get(name).findIndex((i) => cb === i.cb && i.ctx === ctx);
            index > -1 && this.map.get(name).splice(index, 1);
        }
    }
}
